Skip to content

feat: facets for issues and alerts#483

Merged
rbjornstad merged 11 commits into
mainfrom
issue-facets
Jul 1, 2026
Merged

feat: facets for issues and alerts#483
rbjornstad merged 11 commits into
mainfrom
issue-facets

Conversation

@rbjornstad

@rbjornstad rbjornstad commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds facets to IssueConnection and AlertConnection, giving clients distribution counts to build filter UIs without extra round-trips.

Changes

Issues — SQL-aggregate pattern

IssueConnection gains an optional facets field:

type IssueConnection {
  pageInfo: PageInfo!
  nodes: [Issue!]!
  edges: [IssueEdge!]!
  facets: IssueFacets
}

type IssueFacets {
  environments:  [StringFacetItem!]!
  severities:    [IssueSeverityFacetItem!]!
  resourceTypes: [IssueResourceTypeFacetItem!]!
  issueTypes:    [IssueTypeFacetItem!]!
}

New supporting types: IssueSeverityFacetItem, IssueResourceTypeFacetItem, IssueTypeFacetItem.

  • SQL – new FacetsForIssues aggregate query groups by (severity, resource_type, env, issue_type) and returns filtered_count per group. The scope params (scope_resource_type, scope_resource_name, scope_env) are applied in the outer WHERE so only groups that exist within the connection scope appear in the result; user filter params (env[], severity, issue_type, filter_resource_type, filter_resource_name) go inside COUNT(*) FILTER (...). Single extra DB query, executed only when facets is requested.
  • IssueConnection – changed from a type alias to a struct (like ActivityLogEntryConnection) carrying teamSlug, scope, and filter for lazy facet computation in the resolver.
  • IssueScope – separates the fixed resource context (set by the caller, e.g. application.issues fixes resourceType, resourceName, env) from the user-controlled IssueFilter. Scope params narrow the base dataset; filter params narrow the counted set.
  • ComputeFacets – sums filteredCount per dimension; dimension values with filteredCount = 0 are still present because the scope WHERE ensures every group that exists in the scope seeds the map.
  • All six Issues resolvers (team + five resource-level) updated.

Alerts — in-memory pattern

AlertConnection gains an optional facets field:

type AlertConnection {
  pageInfo: PageInfo!
  nodes: [Alert!]!
  edges: [AlertEdge!]!
  facets: AlertFacets
}

type AlertFacets {
  environments: [StringFacetItem!]!
  states:       [AlertStateFacetItem!]!
}

New supporting type: AlertStateFacetItem.

  • AlertConnection – changed from pagination.Connection to pagination.FacetableConnection, following the same pattern as applications, OpenSearch, and Valkey.
  • AlertFacets – computes environment and state distribution counts in-memory over all alerts in the connection. Uses sync.Once to avoid recomputing the filtered set.
  • Both Alerts resolvers (teamResolver, teamEnvironmentResolver) now use SortFilter.PaginatedList.

Tests

IssuesTestBuildFacets covers three cases:

  1. Mixed rows with seeded zero-count values — correct aggregation
  2. With filter — unmatched dimension values present with count 0
  3. Empty input — empty slices, no panics

AlertsTestAlertFacets_Environments and TestAlertFacets_States cover:

  1. No filter — all counts match totals
  2. Filter by a dimension — non-matching values present with count 0
  3. Filter across dimensions — counts reflect intersection
  4. Empty alerts — no facet items

Design choices

  • SQL-aggregate for issues, in-memory for alerts — issues live in the DB and can be numerous; alerts are fetched in full from an external source and already in memory, so in-memory counting is simpler and equally efficient.
  • Scope vs filter split — scope params narrow the base dataset (so only groups that exist in the connection scope appear), filter params narrow the counted set. This follows the activitylog pattern and ensures facet items are never missing due to the current filter.
  • resourceName is not a facet dimension — it is a free-text search field, not a categorical one.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds facet distributions to the Issues GraphQL connection so clients can build filter UIs (counts per environment/severity/resourceType/issueType) without additional round-trips, using a SQL aggregate query and lazy computation when facets is requested.

Changes:

  • Extends IssueConnection in the GraphQL schema with an optional facets field and introduces new facet item types.
  • Adds FacetsForIssues SQL aggregate query + issue.ComputeFacets/buildFacets to compute per-dimension counts.
  • Refactors the issues connection return type from pagination.Connection[Issue] to *issue.IssueConnection across resolvers and regenerates gqlgen/sqlc outputs.

Reviewed changes

Copilot reviewed 13 out of 22 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
internal/issue/queries/issue.sql Adds FacetsForIssues aggregate query for grouped total/filtered counts.
internal/issue/queries.go Changes ListIssues to return *IssueConnection carrying teamSlug/filter.
internal/issue/model.go Replaces IssueConnection type alias with a struct; adds facet DTO types.
internal/issue/issuesql/querier.go Adds FacetsForIssues to the sqlc querier interface.
internal/issue/issuesql/issue.sql.go sqlc-generated implementation/types for FacetsForIssues.
internal/issue/facets.go Implements ComputeFacets and buildFacets assembly/sorting logic.
internal/issue/facets_test.go Unit tests for buildFacets behavior (seeded zeros, empty input).
internal/graph/schema/issues.graphqls Adds IssueConnection.facets and new facet GraphQL types with descriptions.
internal/graph/issues.resolvers.go Adds IssueConnection resolver for facets; updates Team issues return type.
internal/graph/applications.resolvers.go Updates Application.issues resolver to return *issue.IssueConnection.
internal/graph/jobs.resolvers.go Updates Job.issues resolver to return *issue.IssueConnection.
internal/graph/opensearch.resolvers.go Updates OpenSearch.issues resolver to return *issue.IssueConnection.
internal/graph/sqlinstance.resolvers.go Updates SQLInstance.issues resolver to return *issue.IssueConnection.
internal/graph/valkey.resolvers.go Updates Valkey.issues resolver to return *issue.IssueConnection.
internal/graph/gengql/applications.generated.go gqlgen updates for Application.issues returning IssueConnection.
internal/graph/gengql/jobs.generated.go gqlgen updates for Job.issues returning IssueConnection.
internal/graph/gengql/opensearch.generated.go gqlgen updates for OpenSearch.issues returning IssueConnection.
internal/graph/gengql/sqlinstance.generated.go gqlgen updates for SQLInstance.issues returning IssueConnection.
internal/graph/gengql/teams.generated.go gqlgen updates for Team.issues returning IssueConnection.
internal/graph/gengql/valkey.generated.go gqlgen updates for Valkey.issues returning IssueConnection.
internal/graph/gengql/issues.generated.go gqlgen adds IssueConnectionResolver.Facets and marshaling for new facet types.
internal/graph/gengql/root_.generated.go gqlgen registers IssueConnection resolver root + complexity entries.
Files not reviewed (9)
  • internal/graph/gengql/applications.generated.go: Generated file
  • internal/graph/gengql/issues.generated.go: Generated file
  • internal/graph/gengql/jobs.generated.go: Generated file
  • internal/graph/gengql/opensearch.generated.go: Generated file
  • internal/graph/gengql/sqlinstance.generated.go: Generated file
  • internal/graph/gengql/teams.generated.go: Generated file
  • internal/graph/gengql/valkey.generated.go: Generated file
  • internal/issue/issuesql/issue.sql.go: Generated file
  • internal/issue/issuesql/querier.go: Generated file

Comment thread internal/issue/queries/issue.sql
Comment thread internal/graph/issues.resolvers.go

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 22 changed files in this pull request and generated no new comments.

Files not reviewed (9)
  • internal/graph/gengql/applications.generated.go: Generated file
  • internal/graph/gengql/issues.generated.go: Generated file
  • internal/graph/gengql/jobs.generated.go: Generated file
  • internal/graph/gengql/opensearch.generated.go: Generated file
  • internal/graph/gengql/sqlinstance.generated.go: Generated file
  • internal/graph/gengql/teams.generated.go: Generated file
  • internal/graph/gengql/valkey.generated.go: Generated file
  • internal/issue/issuesql/issue.sql.go: Generated file
  • internal/issue/issuesql/querier.go: Generated file

@rbjornstad rbjornstad marked this pull request as ready for review July 1, 2026 07:55
@rbjornstad rbjornstad requested a review from a team as a code owner July 1, 2026 07:55
@rbjornstad rbjornstad marked this pull request as draft July 1, 2026 08:09
@rbjornstad rbjornstad changed the title feat: facets for issues feat: facets for issues and alerts Jul 1, 2026
@rbjornstad rbjornstad requested a review from Copilot July 1, 2026 08:36

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 28 changed files in this pull request and generated 1 comment.

Files not reviewed (10)
  • internal/graph/gengql/alerts.generated.go: Generated file
  • internal/graph/gengql/applications.generated.go: Generated file
  • internal/graph/gengql/issues.generated.go: Generated file
  • internal/graph/gengql/jobs.generated.go: Generated file
  • internal/graph/gengql/opensearch.generated.go: Generated file
  • internal/graph/gengql/sqlinstance.generated.go: Generated file
  • internal/graph/gengql/teams.generated.go: Generated file
  • internal/graph/gengql/valkey.generated.go: Generated file
  • internal/issue/issuesql/issue.sql.go: Generated file
  • internal/issue/issuesql/querier.go: Generated file

Comment thread internal/issue/facets_test.go Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 28 changed files in this pull request and generated no new comments.

Files not reviewed (10)
  • internal/graph/gengql/alerts.generated.go: Generated file
  • internal/graph/gengql/applications.generated.go: Generated file
  • internal/graph/gengql/issues.generated.go: Generated file
  • internal/graph/gengql/jobs.generated.go: Generated file
  • internal/graph/gengql/opensearch.generated.go: Generated file
  • internal/graph/gengql/sqlinstance.generated.go: Generated file
  • internal/graph/gengql/teams.generated.go: Generated file
  • internal/graph/gengql/valkey.generated.go: Generated file
  • internal/issue/issuesql/issue.sql.go: Generated file
  • internal/issue/issuesql/querier.go: Generated file

@rbjornstad rbjornstad marked this pull request as ready for review July 1, 2026 09:02
Introduces facets for the team issues query, following the SQL-aggregate
pattern used by the activity log. Facets provide distribution counts across
four dimensions to help narrow down results:

- environments
- severities
- resourceTypes
- issueTypes

A new FacetsForIssues SQL query computes counts grouped by
(severity, resource_type, env, issue_type) using a FILTER WHERE clause
so that all possible values are seeded (with count 0 if unmatched) and
the filtered counts reflect the current active filter.
Adds IssueFacets type to IssueConnection with four fields:
  environments, severities, resourceTypes, issueTypes

Each field returns a list of facet items with count of matching issues.
Counts respect the current filter but are computed across the full
(unpaginated) result set.

New GraphQL types:
  IssueFacets, SeverityFacetItem, ResourceTypeFacetItem, IssueTypeFacetItem

All Issues resolvers (team + resource-level) now return *issue.IssueConnection
so the connection carries the team slug and filter needed for lazy facet
computation.
Address Copilot review feedback:

- FacetsForIssues SQL: split scope params (resource_type, resource_name, env)
  into the outer WHERE clause so total_count reflects the actual connection
  scope, matching the activitylog FacetsForActivityTypes pattern. User filter
  params remain in COUNT(*) FILTER (...).

- IssueConnection now carries a separate IssueScope for resource-level
  queries (application, job, opensearch, sqlinstance, valkey). Resource
  resolvers build the scope from the resource object instead of embedding
  it in IssueFilter.

- Normalize empty environments slice to nil in ListIssues and ComputeFacets
  so that an empty []string{} does not produce an always-false ANY predicate.
Comment thread internal/graph/schema/issues.graphqls Outdated
…o IssueSeverityFacetItem and IssueResourceTypeFacetItem

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 28 changed files in this pull request and generated 2 comments.

Files not reviewed (10)
  • internal/graph/gengql/alerts.generated.go: Generated file
  • internal/graph/gengql/applications.generated.go: Generated file
  • internal/graph/gengql/issues.generated.go: Generated file
  • internal/graph/gengql/jobs.generated.go: Generated file
  • internal/graph/gengql/opensearch.generated.go: Generated file
  • internal/graph/gengql/sqlinstance.generated.go: Generated file
  • internal/graph/gengql/teams.generated.go: Generated file
  • internal/graph/gengql/valkey.generated.go: Generated file
  • internal/issue/issuesql/issue.sql.go: Generated file
  • internal/issue/issuesql/querier.go: Generated file

Comment thread internal/issue/queries/issue.sql
Comment thread internal/issue/queries/issue.sql

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 28 changed files in this pull request and generated 1 comment.

Files not reviewed (10)
  • internal/graph/gengql/alerts.generated.go: Generated file
  • internal/graph/gengql/applications.generated.go: Generated file
  • internal/graph/gengql/issues.generated.go: Generated file
  • internal/graph/gengql/jobs.generated.go: Generated file
  • internal/graph/gengql/opensearch.generated.go: Generated file
  • internal/graph/gengql/sqlinstance.generated.go: Generated file
  • internal/graph/gengql/teams.generated.go: Generated file
  • internal/graph/gengql/valkey.generated.go: Generated file
  • internal/issue/issuesql/issue.sql.go: Generated file
  • internal/issue/issuesql/querier.go: Generated file

Comment thread internal/issue/queries/issue.sql
@rbjornstad rbjornstad merged commit d9ab892 into main Jul 1, 2026
15 checks passed
@rbjornstad rbjornstad deleted the issue-facets branch July 1, 2026 10:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants